/* bzflag
 * Copyright (c) 1993-2020 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */


#include "common.h"

// implementation header
#include "ObstacleMgr.h"

// system headers
#include <string.h>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <iostream>

// common headers
#include "Pack.h"
#include "MeshTransform.h"
#include "ObstacleModifier.h"
#include "StateDatabase.h"
#include "PhysicsDriver.h"
#include "BzMaterial.h"
#include "MeshDrawInfo.h"

// obstacle headers
#include "Obstacle.h"
#include "WallObstacle.h"
#include "BoxBuilding.h"
#include "PyramidBuilding.h"
#include "BaseBuilding.h"
#include "Teleporter.h"
#include "MeshObstacle.h"
#include "ArcObstacle.h"
#include "ConeObstacle.h"
#include "SphereObstacle.h"
#include "TetraBuilding.h"


//////////////////////////////////////////////////////////////////////////////
//
// Group Instance
// - uses a group definition and a transform to produce obstacles
//


void GroupInstance::init()
{
    modifyTeam = false;
    team = 0;
    modifyColor = false;
    tint[0] = tint[1] = tint[2] = tint[3] = 1.0f;
    modifyPhysicsDriver = false;
    phydrv = -1;
    modifyMaterial = false;
    material = NULL;
    driveThrough = false;
    shootThrough = false;
    ricochet     = false;

    return;
}


GroupInstance::GroupInstance(const std::string& _groupdef)
{
    groupdef = _groupdef;
    init();
    return;
}


GroupInstance::GroupInstance()
{
    init();
    return;
}


GroupInstance::~GroupInstance()
{
    return;
}


void GroupInstance::setTransform(const MeshTransform& _transform)
{
    transform = _transform;
    return;
}


void GroupInstance::setName(const std::string& _name)
{
    name = _name;
    return;
}


const std::string& GroupInstance::getName() const
{
    return name;
}


void GroupInstance::setTeam(int _team)
{
    team = _team;
    modifyTeam = true;
    return;
}


void GroupInstance::setTint(const float _tint[4])
{
    memcpy(tint, _tint, sizeof(float[4]));
    modifyColor = true;
    return;
}


void GroupInstance::setPhysicsDriver(int _phydrv)
{
    phydrv = _phydrv;
    modifyPhysicsDriver = true;
    return;
}


void GroupInstance::setMaterial(const BzMaterial* _material)
{
    material = _material;
    modifyMaterial = true;
    return;
}


void GroupInstance::setDriveThrough()
{
    driveThrough = true;
    return;
}


void GroupInstance::setShootThrough()
{
    shootThrough = true;
    return;
}

void GroupInstance::setCanRicochet()
{
    ricochet = true;
    return;
}


void GroupInstance::addMaterialSwap(const BzMaterial* srcMat,
                                    const BzMaterial* dstMat)
{
    matMap[srcMat] = dstMat;
    return;
}


const std::string& GroupInstance::getGroupDef() const
{
    return groupdef;
}


const MeshTransform& GroupInstance::getTransform() const
{
    return transform;
}


void* GroupInstance::pack(void* buf)
{
    buf = nboPackStdString(buf, groupdef);

    if (matMap.size() <= 0)
        buf = nboPackStdString(buf, name);
    else
    {
        // hack to stuff in material map data
        std::string fakeString = name;
        const unsigned int origSize = fakeString.size();
        const unsigned int fakeSize =
            (1 + sizeof(int32_t) + (matMap.size() * 2 * sizeof(int32_t)));
        fakeString.resize(origSize + fakeSize);
        char* buffer = new char[fakeSize];
        void* p;
        int count = matMap.size();
        p = nboPackUByte(buffer, 0); // terminate
        p = nboPackInt(p, count);
        MaterialMap::const_iterator it;
        for (it = matMap.begin(); it != matMap.end(); ++it)
        {
            int srcIndex = MATERIALMGR.getIndex(it->first);
            int dstIndex = MATERIALMGR.getIndex(it->second);
            p = nboPackInt(p, srcIndex);
            p = nboPackInt(p, dstIndex);
        }
        for (unsigned int i = 0; i < fakeSize; i++)
            fakeString[origSize + i] = buffer[i];
        delete[] buffer;
        buf = nboPackStdString(buf, fakeString);
    }

    buf = transform.pack(buf);

    uint8_t bits = 0;
    if (modifyTeam)      bits |= (1 << 0);
    if (modifyColor)     bits |= (1 << 1);
    if (modifyPhysicsDriver) bits |= (1 << 2);
    if (modifyMaterial)      bits |= (1 << 3);
    if (driveThrough)    bits |= (1 << 4);
    if (shootThrough)    bits |= (1 << 5);
    if (ricochet)     bits |= (1 << 6);
    buf = nboPackUByte(buf, bits);

    if (modifyTeam)
        buf = nboPackUShort(buf, team);
    if (modifyColor)
    {
        buf = nboPackVector(buf, tint);
        buf = nboPackFloat(buf, tint[3]);
    }
    if (modifyPhysicsDriver)
        buf = nboPackInt(buf, phydrv);
    if (modifyMaterial)
    {
        int matindex = MATERIALMGR.getIndex(material);
        buf = nboPackInt(buf, (int32_t) matindex);
    }

    return buf;
}


const void* GroupInstance::unpack(const void* buf)
{
    buf = nboUnpackStdString(buf, groupdef);

    buf = nboUnpackStdStringRaw(buf, name);
    if (strlen(name.c_str()) != name.size())
    {
        // hack to extract material map data
        const void* p = name.c_str() + strlen(name.c_str()) + 1;
        nboUseErrorChecking(false);
        {
            int32_t count;
            p = nboUnpackInt(p, count);
            for (int i = 0; i < count; i++)
            {
                int32_t srcIndex, dstIndex;
                p = nboUnpackInt(p, srcIndex);
                p = nboUnpackInt(p, dstIndex);
                const BzMaterial* srcMat = MATERIALMGR.getMaterial(srcIndex);
                const BzMaterial* dstMat = MATERIALMGR.getMaterial(dstIndex);
                matMap[srcMat] = dstMat;
            }
        }
        nboUseErrorChecking(true);
        name.resize(strlen(name.c_str())); // clean up the name
    }

    buf = transform.unpack(buf);

    uint8_t bits;
    buf = nboUnpackUByte(buf, bits);
    modifyTeam =      ((bits & (1 << 0)) == 0) ? false : true;
    modifyColor =     ((bits & (1 << 1)) == 0) ? false : true;
    modifyPhysicsDriver = ((bits & (1 << 2)) == 0) ? false : true;
    modifyMaterial =  ((bits & (1 << 3)) == 0) ? false : true;
    driveThrough =    ((bits & (1 << 4)) == 0) ? false : true;
    shootThrough =    ((bits & (1 << 5)) == 0) ? false : true;
    ricochet =        ((bits & (1 << 6)) == 0) ? false : true;

    if (modifyTeam)
    {
        uint16_t tmpTeam;
        buf = nboUnpackUShort(buf, tmpTeam);
        team = (int)tmpTeam;
    }
    if (modifyColor)
    {
        buf = nboUnpackVector(buf, tint);
        buf = nboUnpackFloat(buf, tint[3]);
    }
    if (modifyPhysicsDriver)
    {
        int32_t inPhyDrv;
        buf = nboUnpackInt(buf, inPhyDrv);
        phydrv = int(inPhyDrv);
    }
    if (modifyMaterial)
    {
        int32_t matindex;
        buf = nboUnpackInt(buf, matindex);
        material = MATERIALMGR.getMaterial(matindex);
    }

    return buf;
}


int GroupInstance::packSize()
{
    int fullSize = 0;
    fullSize += nboStdStringPackSize(groupdef);

    fullSize += nboStdStringPackSize(name);
    if (matMap.size() > 0)
    {
        fullSize += 1; // terminator
        fullSize += sizeof(int32_t); // count;
        fullSize += matMap.size() * 2 * sizeof(int32_t);
    }

    fullSize += transform.packSize();
    fullSize += sizeof(uint8_t);
    if (modifyTeam)
        fullSize += sizeof(uint16_t);
    if (modifyColor)
        fullSize += sizeof(float[4]);
    if (modifyPhysicsDriver)
        fullSize += sizeof(int32_t);
    if (modifyMaterial)
        fullSize += sizeof(int32_t);
    return fullSize;
}


void GroupInstance::print(std::ostream& out, const std::string& indent) const
{
    out << indent << "group " << groupdef << std::endl;

    if (name.size() > 0)
        out << indent << "  name " << name << std::endl;

    transform.printTransforms(out, indent);
    if (modifyTeam)
        out << indent << "  team " << team << std::endl;
    if (modifyColor)
    {
        out << indent << "  tint " << tint[0] << " " << tint[1] << " "
            << tint[2] << " " << tint[3] << " "
            << std::endl;
    }
    if (modifyPhysicsDriver)
    {
        const PhysicsDriver* driver = PHYDRVMGR.getDriver(phydrv);
        if (driver != NULL)
        {
            out << indent << "  phydrv ";
            if (driver->getName().size() > 0)
                out << driver->getName();
            else
                out << phydrv;
            out << std::endl;
        }
    }
    if (modifyMaterial)
    {
        out << indent << "  matref ";
        MATERIALMGR.printReference(out, material);
        out << std::endl;
    }
    else if (matMap.size() > 0)
    {
        MaterialMap::const_iterator it;
        for (it = matMap.begin(); it != matMap.end(); ++it)
        {
            out << indent << "  matswap ";
            MATERIALMGR.printReference(out, it->first);
            out << " ";
            MATERIALMGR.printReference(out, it->second);
            out << std::endl;
        }
    }

    if (driveThrough)
        out << indent << "  driveThrough"  << std::endl;
    if (shootThrough)
        out << indent << "  shootThrough"  << std::endl;
    if (ricochet)
        out << indent << "  ricochet"      << std::endl;
    out << indent << "end" << std::endl;

    return;
}


//////////////////////////////////////////////////////////////////////////////
//
// Group Definition
// - defines an obstacle group
//

std::string GroupDefinition::depthName;


GroupDefinition::GroupDefinition(const std::string& _name)
{
    name = _name;
    active = false;
    return;
}


GroupDefinition::~GroupDefinition()
{
    return;
}


Obstacle* GroupDefinition::newObstacle(int type)
{
    Obstacle* obs = NULL;

    if (type == wallType)
        obs = new WallObstacle();
    else if (type == boxType)
        obs = new BoxBuilding();
    else if (type == pyrType)
        obs = new PyramidBuilding();
    else if (type == baseType)
        obs = new BaseBuilding();
    else if (type == teleType)
        obs = new Teleporter();
    else if (type == meshType)
        obs = new MeshObstacle();
    else if (type == arcType)
        obs = new ArcObstacle();
    else if (type == coneType)
        obs = new ConeObstacle();
    else if (type == sphereType)
        obs = new SphereObstacle();
    else if (type == tetraType)
        obs = new TetraBuilding();

    return obs;
}


void GroupDefinition::addObstacle(Obstacle* obstacle)
{
    const char* type = obstacle->getType();

    if (WallObstacle::getClassName() == type)
        lists[wallType].push_back(obstacle);
    else if (BoxBuilding::getClassName() == type)
        lists[boxType].push_back(obstacle);
    else if (BaseBuilding::getClassName() == type)
        lists[baseType].push_back(obstacle);
    else if (PyramidBuilding::getClassName() == type)
        lists[pyrType].push_back(obstacle);
    else if (Teleporter::getClassName() == type)
        lists[teleType].push_back(obstacle);
    else if (MeshObstacle::getClassName() == type)
        lists[meshType].push_back(obstacle);
    else if (ArcObstacle::getClassName() == type)
        lists[arcType].push_back(obstacle);
    else if (ConeObstacle::getClassName() == type)
        lists[coneType].push_back(obstacle);
    else if (SphereObstacle::getClassName() == type)
        lists[sphereType].push_back(obstacle);
    else if (TetraBuilding::getClassName() == type)
        lists[tetraType].push_back(obstacle);
    else
    {
        printf ("GroupDefinition::addObstacle() ERROR: type = %s\n", type);
        exit(1);
    }

    return;
}


void GroupDefinition::addGroupInstance(GroupInstance* group)
{
    groups.push_back(group);
    return;
}


static bool isContainer(int type)
{
    switch (type)
    {
    case GroupDefinition::arcType:
    case GroupDefinition::coneType:
    case GroupDefinition::sphereType:
    case GroupDefinition::tetraType:
        return true;
    default:
        return false;
    }
}


void GroupDefinition::makeTeleName(Obstacle* obs, unsigned int pos) const
{
    Teleporter* tele = (Teleporter*) obs;
    std::string fullname = depthName;
    if (tele->getName().size() > 0)
        fullname += tele->getName();
    else
    {
        // make the default name
        fullname += "/t";
        char buffer[8];
        sprintf (buffer, "%i", pos);
        fullname += buffer;
    }
    tele->setName(fullname);
    return;
}


void GroupDefinition::appendGroupName(const GroupInstance* group) const
{
    std::string newName;
    if (group->getName().size() > 0)
        newName = group->getName();
    else
    {
        // make the default name
        int count = 0;
        for (unsigned int i = 0; i < groups.size(); i++)
        {
            const GroupInstance* g = groups[i];
            if (g == group)
                break;
            if (g->getGroupDef() == group->getGroupDef())
                count++;
        }
        newName = "/";
        newName += group->getGroupDef();
        newName += "/";
        std::stringstream buffer;
        buffer << count;
        newName += buffer.str();
    }
    depthName += newName;
    depthName += ":";
    return;
}


static MeshObstacle* makeContainedMesh(int type, Obstacle* obs)
{
    MeshObstacle* mesh = NULL;
    switch (type)
    {
    case GroupDefinition::arcType:
    {
        mesh = ((ArcObstacle*)obs)->makeMesh();
        break;
    }
    case GroupDefinition::coneType:
    {
        mesh = ((ConeObstacle*)obs)->makeMesh();
        break;
    }
    case GroupDefinition::sphereType:
    {
        mesh = ((SphereObstacle*)obs)->makeMesh();
        break;
    }
    case GroupDefinition::tetraType:
    {
        mesh = ((TetraBuilding*)obs)->makeMesh();
        break;
    }
    }
    return mesh;
}


void GroupDefinition::makeGroups(const MeshTransform& xform,
                                 const ObstacleModifier& obsMod) const
{
    if (active)
    {
        logDebugMessage(1,"warning: avoided recursion, groupdef \"%s\"\n", name.c_str());
        return; // avoid recursion
    }

    active = true;

    const bool isWorld = (this == OBSTACLEMGR.getWorld());
    char groupDefBit = isWorld ? Obstacle::WorldSource :
                       Obstacle::GroupDefSource;

    for (int type = 0; type < ObstacleTypeCount; type++)
    {
        const ObstacleList& list = lists[type];
        for (unsigned int i = 0; i < list.size(); i++)
        {
            Obstacle* obs;
            if (isWorld)
            {
                obs = list[i]; // no need to copy
            }
            else
                obs = list[i]->copyWithTransform(xform);

            // the tele names are setup with default names if
            // they are not named (even for those in the world
            // groupd def). invalid teleporters are also named
            if (type == teleType)
                makeTeleName(obs, i);

            if (obs->isValid())
            {
                if (!isWorld)
                {
                    // add it to the world
                    // (this will also add container obstacles into the world group)
                    obs->setSource(Obstacle::GroupDefSource);
                    obsMod.execute(obs);
                    OBSTACLEMGR.addWorldObstacle(obs);
                    // add a modified MeshDrawInfo to the new mesh, if applicable
                    if (type == meshType)
                    {
                        const MeshObstacle* source = (const MeshObstacle*) list[i];
                        const MeshDrawInfo* diSource = source->getDrawInfo();
                        if ((diSource != NULL) &&
                                (!diSource->isServerSide()) && diSource->isValid())
                        {
                            MaterialSet matSet;
                            MaterialMap matMap;
                            diSource->getMaterials(matSet);
                            obsMod.getMaterialMap(matSet, matMap);
                            MeshDrawInfo* diDest = new MeshDrawInfo(diSource, xform, matMap);
                            MeshObstacle* dest = (MeshObstacle*) obs;
                            dest->setDrawInfo(diDest);
                        }
                    }
                }
                // generate contained meshes
                // (always get placed into the world group)
                MeshObstacle* mesh = makeContainedMesh(type, obs);
                if ((mesh != NULL) && mesh->isValid())
                {
                    mesh->setSource(Obstacle::ContainerSource | groupDefBit);
                    obsMod.execute(mesh);
                    OBSTACLEMGR.addWorldObstacle(mesh);
                }
            }
        }
    }

    for (unsigned int i = 0; i < groups.size(); i++)
    {
        const GroupInstance* group = groups[i];
        const GroupDefinition* groupDef =
            OBSTACLEMGR.findGroupDef(group->getGroupDef());
        if (groupDef != NULL)
        {
            // make the new depth name
            std::string tmpDepthName = depthName;
            appendGroupName(group);

            // setup the transform and modifier
            ObstacleModifier newObsMod(obsMod, *group);
            MeshTransform tmpXform = xform;
            tmpXform.prepend(group->getTransform());

            // recurse and make plentiful
            groupDef->makeGroups(tmpXform, newObsMod);

            // revert to the old depth name
            depthName = tmpDepthName;
        }
        else
        {
            logDebugMessage(1,"warning: group definition \"%s\" is missing\n",
                            group->getGroupDef().c_str());
        }
    }

    active = false;

    return;
}


void GroupDefinition::replaceBasesWithBoxes()
{
    ObstacleList& list = lists[baseType];
    for (unsigned int i = 0; i < list.size(); i++)
    {
        BaseBuilding* base = (BaseBuilding*) list[i];
        const float* baseSize = base->getSize();
        BoxBuilding* box =
            new BoxBuilding(base->getPosition(), base->getRotation(),
                            baseSize[0], baseSize[1], baseSize[2],
                            base->isDriveThrough(), base->isShootThrough(),
                            base->canRicochet(), false);
        delete base;
        list.remove(i);
        i--;
        addObstacle(box);
    }
    return;
}


void GroupDefinition::clear()
{
    for (int type = 0; type < ObstacleTypeCount; type++)
    {
        ObstacleList& list = lists[type];
        for (unsigned int i = 0; i < list.size(); i++)
            delete list[i];
        list.clear();
    }

    for (unsigned int i = 0; i < groups.size(); i++)
        delete groups[i];
    groups.clear();

    return;
}


void GroupDefinition::tighten()
{
    for (int type = 0; type < ObstacleTypeCount; type++)
        lists[type].tighten();
    return;
}


void GroupDefinition::sort(int (*compare)(const void* a, const void* b))
{
    if (this != OBSTACLEMGR.getWorld())
    {
        return; // only sort the world groupdef
    }
    for (int type = 0; type < ObstacleTypeCount; type++)
        lists[type].sort(compare);
    return;
}


void GroupDefinition::deleteInvalidObstacles()
{
    if (this != OBSTACLEMGR.getWorld())
    {
        return; // only delete invalid obstacles in the world groupdef
    }
    for (int type = 0; type < ObstacleTypeCount; type++)
    {
        ObstacleList& list = lists[teleType];
        for (unsigned int i = 0; i < list.size(); i++)
        {
            Obstacle* obs = list[i];
            if (!obs->isValid())
            {
                logDebugMessage(1,"Deleted invalid %s obstacle\n", obs->getType());
                delete obs;
                list.remove(i);
                i--; // don't miss the substitute
            }
        }
    }
    return;
}


void GroupDefinition::getSourceMeshes(std::vector<MeshObstacle*>& meshes) const
{
    const bool isWorld = (this == OBSTACLEMGR.getWorld());

    const ObstacleList& list = lists[meshType];
    for (unsigned int i = 0; i < list.size(); i++)
    {
        MeshObstacle* mesh = (MeshObstacle*)list[i];
        if (!isWorld || mesh->isFromWorldFile())
        {
            const int listSize = (int)meshes.size();
            int j;
            for (j = 0; j < listSize; j++)
            {
                if (meshes[j] == mesh)
                    break;
            }
            if (j == listSize)
            {
                meshes.push_back(mesh); // a new entry
            }
        }
    }

    for (unsigned int i = 0; i < groups.size(); i++)
    {
        const GroupInstance* group = groups[i];
        const GroupDefinition* groupDef =
            OBSTACLEMGR.findGroupDef(group->getGroupDef());
        if (groupDef != NULL)
            groupDef->getSourceMeshes(meshes);
    }

    return;
}


void* GroupDefinition::pack(void* buf) const
{
    buf = nboPackStdString(buf, name);

    unsigned int i;
    for (int type = 0; type < ObstacleTypeCount; type++)
    {
        const ObstacleList& list = getList(type);
        int count = 0;
        for (i = 0; i < list.size(); i++)
        {
            if (list[i]->isFromWorldFile())
                count++;
        }
        buf = nboPackUInt(buf, count);
        for (i = 0; i < list.size(); i++)
        {
            if (list[i]->isFromWorldFile())
                buf = list[i]->pack(buf);
        }
    }

    buf = nboPackUInt(buf, groups.size());
    for (i = 0; i < groups.size(); i++)
        buf = groups[i]->pack(buf);

    return buf;
}


const void* GroupDefinition::unpack(const void* buf)
{
    buf = nboUnpackStdString(buf, name);

    uint32_t i, count;

    for (int type = 0; type < ObstacleTypeCount; type++)
    {
        buf = nboUnpackUInt(buf, count);
        for (i = 0; i < count; i++)
        {
            Obstacle* obs = newObstacle(type);
            if (obs != NULL)
            {
                buf = obs->unpack(buf);
                if (obs->isValid())
                    lists[type].push_back(obs);
            }
        }
    }

    buf = nboUnpackUInt(buf, count);
    for (i = 0; i < count; i++)
    {
        GroupInstance* group = new GroupInstance;
        buf = group->unpack(buf);
        addGroupInstance(group);
    }

    return buf;
}


int GroupDefinition::packSize() const
{
    int fullSize = 0;

    fullSize += nboStdStringPackSize(name);

    for (int type = 0; type < ObstacleTypeCount; type++)
    {
        fullSize += sizeof(uint32_t);
        const ObstacleList& list = getList(type);
        for (unsigned int i = 0; i < list.size(); i++)
        {
            if (list[i]->isFromWorldFile())
                fullSize += list[i]->packSize();
        }
    }
    fullSize += sizeof(uint32_t);
    for (unsigned int i = 0; i < groups.size(); i++)
        fullSize += groups[i]->packSize();

    return fullSize;
}


void GroupDefinition::printGrouped(std::ostream& out,
                                   const std::string& indent) const
{
    const bool isWorld = (this == OBSTACLEMGR.getWorld());
    const bool saveAsMeshes = BZDB.isTrue("saveAsMeshes");
    std::string myIndent = indent;

    // deal with indenting
    if (!isWorld)
    {
        myIndent += "  ";
        out << indent << "define " << name << std::endl;
    }

    // print the obstacles
    for (int type = 0; type < ObstacleTypeCount; type++)
    {
        const ObstacleList& list = getList(type);
        for (unsigned int i = 0; i < list.size(); i++)
        {
            Obstacle* obs = list[i];

            if (!obs->isFromGroupDef() && !obs->isFromContainer())
            {
                if (!saveAsMeshes)
                {
                    if (!obs->isFromContainer())
                        obs->print(out, myIndent);
                }
                else
                {
                    if (!isContainer(type))
                        obs->print(out, myIndent);
                    else
                    {
                        // rebuild the mesh  (ya, just to print it)
                        MeshObstacle* mesh = makeContainedMesh(type, obs);
                        if ((mesh != NULL) && (mesh->isValid()))
                            mesh->print(out, myIndent);
                        delete mesh;
                    }
                }
            }
        }
    }

    // print the groups
    for (unsigned int i = 0; i < groups.size(); i++)
        groups[i]->print(out, myIndent);

    // deal with indenting
    if (!isWorld)
        out << indent << "enddef" << std::endl << std::endl;

    return;
}


void GroupDefinition::printFlatFile(std::ostream& out,
                                    const std::string& indent) const
{
    const bool saveAsOBJ = BZDB.isTrue("saveAsOBJ");
    const bool saveAsMeshes = BZDB.isTrue("saveAsMeshes");

    // print the obstacles
    for (int type = 0; type < ObstacleTypeCount; type++)
    {
        const ObstacleList& list = getList(type);
        for (unsigned int i = 0; i < list.size(); i++)
        {
            const Obstacle* obs = list[i];

            if (!saveAsMeshes)
            {
                if (!obs->isFromContainer())
                    obs->print(out, indent);
            }
            else
            {
                if (!isContainer(type))
                {
                    if (!saveAsOBJ)
                        obs->print(out, indent);
                    else
                        obs->printOBJ(out, indent);
                }
            }
        }
    }

    return;
}


void GroupDefinition::clearDepthName()
{
    depthName = "";
    return;
}


//////////////////////////////////////////////////////////////////////////////
//
// Group Definition Manager
// - utility class to keep track of group definitions
//

GroupDefinitionMgr OBSTACLEMGR;


GroupDefinitionMgr::GroupDefinitionMgr() : world("")
{
    return;
}


GroupDefinitionMgr::~GroupDefinitionMgr()
{
    clear();
    return;
}


void GroupDefinitionMgr::clear()
{
    world.clear();
    for (unsigned int i = 0; i < list.size(); i++)
    {
        list[i]->clear();
        delete list[i];
    }
    list.clear();
    return;
}


void GroupDefinitionMgr::tighten()
{
    for (unsigned int i = 0; i < list.size(); i++)
        list[i]->tighten();
    world.tighten();
    return;
}


static int compareHeights(const void* a, const void* b)
{
    const Obstacle* obsA = *((const Obstacle* const *)a);
    const Obstacle* obsB = *((const Obstacle* const *)b);
    const Extents& eA = obsA->getExtents();
    const Extents& eB = obsB->getExtents();

    if (eA.maxs[2] > eB.maxs[2])
        return -1;
    else
        return +1;
}


void GroupDefinitionMgr::makeWorld()
{
    GroupDefinition::clearDepthName();

    MeshTransform noXform;
    ObstacleModifier noMods;

    world.makeGroups(noXform, noMods);

    world.deleteInvalidObstacles();

    // sort from top to bottom for enhanced radar
    for (int type = 0; type < GroupDefinition::ObstacleTypeCount; type++)
        world.sort(compareHeights);

    tighten();

    return;
}


void GroupDefinitionMgr::replaceBasesWithBoxes()
{
    world.replaceBasesWithBoxes();
}


void GroupDefinitionMgr::addWorldObstacle(Obstacle* obstacle)
{
    world.addObstacle(obstacle);
    return;
}


void GroupDefinitionMgr::addGroupDef(GroupDefinition* groupdef)
{
    if (groupdef->getName().size() > 0)
        list.push_back(groupdef);
    else
        delete groupdef;
    return;
}


GroupDefinition* GroupDefinitionMgr::findGroupDef(const std::string& name) const
{
    if (name.size() <= 0)
        return NULL;
    for (unsigned int i = 0; i < list.size(); i++)
    {
        if (name == list[i]->getName())
            return list[i];
    }
    return NULL;
}


void GroupDefinitionMgr::getSourceMeshes(std::vector<MeshObstacle*>& meshes) const
{
    meshes.clear();
    world.getSourceMeshes(meshes);
    return;
}


void* GroupDefinitionMgr::pack(void* buf) const
{
    buf = world.pack(buf);
    buf = nboPackUInt(buf, list.size());
    for (unsigned int i = 0; i < list.size(); i++)
        buf = list[i]->pack(buf);
    return buf;
}


const void* GroupDefinitionMgr::unpack(const void* buf)
{
    buf = world.unpack(buf);
    uint32_t i, count;
    buf = nboUnpackUInt(buf, count);
    for (i = 0; i < count; i++)
    {
        GroupDefinition* groupdef = new GroupDefinition("");
        buf = groupdef->unpack(buf);
        addGroupDef(groupdef);
    }
    return buf;
}


int GroupDefinitionMgr::packSize() const
{
    int fullSize = 0;

    fullSize += world.packSize();
    fullSize += sizeof(uint32_t);
    for (unsigned int i = 0; i < list.size(); i++)
        fullSize += list[i]->packSize();
    return fullSize;
}


void GroupDefinitionMgr::print(std::ostream& out,
                               const std::string& indent) const
{
    const bool saveAsOBJ = BZDB.isTrue("saveAsOBJ");
    const bool saveFlatFile = BZDB.isTrue("saveFlatFile");
    const bool saveAsMeshes = BZDB.isTrue("saveAsMeshes");
    if (saveAsOBJ)
    {
        BZDB.set("saveAsMeshes", "1");
        BZDB.set("saveFlatFile", "1");
    }

    // for unique OBJ mesh ids
    Obstacle::resetObjCounter();

    if (!(saveFlatFile || saveAsOBJ))
    {
        // print the group definitions
        for (unsigned int i = 0; i < list.size(); i++)
            list[i]->printGrouped(out, indent);
        // print the world
        world.printGrouped(out, indent);
    }
    else
    {
        // print the world
        world.printFlatFile(out, indent);
    }

    if (saveAsOBJ)
    {
        BZDB.set("saveAsMeshes", saveAsMeshes ? "1" : "0");
        BZDB.set("saveFlatFile", saveFlatFile ? "1" : "0");
    }

    return;
}


// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
